package com.wao.base.repository;

import com.wao.base.domain.BaseMultitenantEntity;
import com.wao.base.domain.BaseTreeableEntity;
import com.wao.base.security.tenant.TenantIdentifierResolver;
import org.hibernate.Filter;
import org.hibernate.Session;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Transactional(readOnly = true)
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
    implements BaseRepository<T, ID> {

    private final EntityManager em;

    public BaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
        super(domainClass, em);
        this.em = em;
    }

    @Override
    @Transactional
    public <S extends T> S save(S entity) {
        if (entity instanceof BaseMultitenantEntity) {
            final BaseMultitenantEntity multitenantEntity = (BaseMultitenantEntity) entity;
            Long wa_id = Long.parseLong(TenantIdentifierResolver.getCurrentTenantIdentifier());
            multitenantEntity.setWaId(wa_id);
            entity = (S) multitenantEntity;
        }
        if (entity instanceof BaseTreeableEntity) {
            final BaseTreeableEntity btEntity = (BaseTreeableEntity) entity;
            boolean childrenNeedChange = false;
            T parent = null;
            if (btEntity.getParent() != null && btEntity.getParent().getId() != null) {
                parent = em.getReference(getDomainClass(), btEntity.getParent().getId());

            }
            if (btEntity.isNew()) {
                btEntity.setFullId("");
                super.save((S) btEntity);
                super.flush();
            } else {
                Long parentChanged = 0l;
                List<Long> totals = super
                    .getCountQuery(createParentExistSpecification((ID) btEntity.getId(), parent),
                        super.getDomainClass()).getResultList();
                Long total = 0L;
                for (Long element : totals) {
                    total += element == null ? 0 : element;
                }
                childrenNeedChange = total <= 0l;
            }
            String fullId = String.valueOf(btEntity.getId()) + ".";
            if (parent != null)
                fullId = ((BaseTreeableEntity) parent).getFullId() + fullId;
            btEntity.setFullId(fullId);
            btEntity.setLevel(fullId.split("\\.").length);
            super.save((S) btEntity);
            List<T> children = super.findAll(createParentSpecification((T) btEntity));
            if (childrenNeedChange && children != null && !children.isEmpty()) {
                for (T c : children) {
                    save(c);
                }
            }
        } else {
            super.save(entity);
        }
        return entity;
    }

    @Override
    public List<T> findAll() {
        Filter filter = (Filter) em.unwrap(Session.class).enableFilter("waFilter");
        filter.setParameter("wa_id", Long.parseLong(TenantIdentifierResolver.getCurrentTenantIdentifier()));
        List<T> list = super.findAll();
        em.unwrap(Session.class).disableFilter("waFilter");
        return list;
    }

    @Override
    public T findOne(ID id) {
        Filter filter = (Filter) em.unwrap(Session.class).enableFilter("waFilter");
        filter.setParameter("wa_id", Long.parseLong(TenantIdentifierResolver.getCurrentTenantIdentifier()));
        T entity = super.findOne(id);
        em.unwrap(Session.class).disableFilter("waFilter");
        return entity;
    }

    @Override
    @Transactional
    public void delete(ID id) {
        Filter filter = (Filter) em.unwrap(Session.class).enableFilter("waFilter");
        filter.setParameter("wa_id", Long.parseLong(TenantIdentifierResolver.getCurrentTenantIdentifier()));
        super.delete(id);
        em.unwrap(Session.class).disableFilter("waFilter");
    }

    private Specification<T> createParentExistSpecification(ID id, T parent) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicate = new ArrayList<>();
                predicate.add(cb.equal(root.get("id"), id));
                predicate.add(cb.equal(root.get("parent"), parent));
                if (parent != null && ((BaseTreeableEntity) parent).getFullId() != null)
                    predicate.add(cb.like(root.get("fullId"), ((BaseTreeableEntity) parent).getFullId() + "%"));
                Predicate[] pre = new Predicate[predicate.size()];
                return query.where(predicate.toArray(pre)).getRestriction();
            }
        };
    }

    private Specification<T> createParentSpecification(T parent) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicate = new ArrayList<>();
                predicate.add(cb.equal(root.get("parent"), parent));
                Predicate[] pre = new Predicate[predicate.size()];
                return query.where(predicate.toArray(pre)).getRestriction();
            }
        };
    }
}
